package com.inspectortime.webapp;

import java.io.Serializable;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;

import com.inspectortime.domain.InspectorCompany;
import com.inspectortime.domain.pricing.PriceAdjusterException;
import com.inspectortime.domain.type.FoundationType;
import com.inspectortime.domain.type.InspectionStatus;
import com.inspectortime.domain.type.PropertyType;
import com.inspectortime.domain.type.State;
import com.inspectortime.repository.Factory;
import com.inspectortime.repository.Repository;
import com.inspectortime.service.ZipCodeSearchService;
import com.inspectortime.util.SpringValidatorUtils;
import com.inspectortime.webapp.util.CustomStateEditor;
import com.inspectortime.webapp.util.FlexibleNumberPropertyEditor;
import com.inspectortime.webapp.util.HttpSessionUtils;
import com.inspectortime.webapp.util.RequestPathUtils;

/**
 * 
 * @author mkvalsvik
 * 
 */
public class InspectionPropertyInformationController extends
		SimpleFormController {

	private Factory factory;
	private Repository repository;
	private static Log log = LogFactory
			.getLog(InspectionPropertyInformationController.class);

	private ZipCodeSearchService zipCodeSearchService;

	public InspectionPropertyInformationController() {
		super();
	}

	protected Object formBackingObject(HttpServletRequest request)
			throws Exception {

		InspectionCriteria inspectionCriteria = new InspectionCriteria();

		// This flag is used to indicate whether or not an Inspection object has
		// already been started
		String wizardStarted = request.getParameter("wizardStarted");
		if (StringUtils.hasText(wizardStarted)
				&& Boolean.valueOf(wizardStarted) == true) {
			inspectionCriteria.setInspection(HttpSessionUtils
					.retrieveWizardInspection(request, repository));
		} else {
			inspectionCriteria.setInspection(factory.createInspection());
			inspectionCriteria.getInspection().setWebBooked(true);
		}

		// This flag indicates whether or not we came from an IT search results
		// page. If so retain IT skinning for the remainder of this user's http
		// session.
		String fromSearch = request.getParameter("fromSearch");
		if (StringUtils.hasText(fromSearch)
				&& Boolean.valueOf(fromSearch) == true) {
			HttpSessionUtils.enableFromSearchFlag(request);
		}

		String companyShortName = RequestPathUtils
				.extractInspectorCompanyShortNameFromPath(request);
		InspectorCompany inspectorCompany = repository
				.findInspectorCompanyByShortName(companyShortName);
		inspectionCriteria.setInspectorCompany(inspectorCompany);
		log.info("Showing booking wizard for: " + companyShortName);

		return inspectionCriteria;

	}

	protected ModelAndView onSubmit(HttpServletRequest request,
			HttpServletResponse response, Object command, BindException errors)
			throws Exception {

		InspectionCriteria inspectionCriteria = (InspectionCriteria) command;

		// Set status if not set already
		if (inspectionCriteria.getInspection().getInspectionStatus() == null) {
			inspectionCriteria.getInspection().setInspectionStatus(
					InspectionStatus.INCOMPLETE);
		}

		// We save the request URL so that this can be used to build a base path
		// for e-mail notifications
		inspectionCriteria.getInspection().setRequestUrl(
				request.getRequestURL().toString());

		Serializable inspectionId = inspectionCriteria.getInspection().save();
		HttpSessionUtils.storeWizardInspection(request, inspectionId);

		return new ModelAndView(this.getSuccessView());
	}

	protected Map referenceData(HttpServletRequest request) throws Exception {
		Map<String, Object> data = new HashMap<String, Object>();
		data.put("foundationTypes", FoundationType.values());
		data.put("propertyTypes", PropertyType.values());
		data.put("formValidator", this.getValidator());

		String inspectorCompanyShortName = RequestPathUtils
				.extractInspectorCompanyShortNameFromPath(request);
		if (inspectorCompanyShortName != null) {
			InspectorCompany inspectorCompany = repository
					.findInspectorCompanyByShortName(inspectorCompanyShortName);
			if (inspectorCompany == null) {
				throw new RuntimeException("Inspector company "
						+ inspectorCompanyShortName + " not found in database.");
			}
			data.put("inspectorCompany", inspectorCompany);
			data.put("states", findServedStates(inspectorCompany));
		}

		return data;
	}

	/**
	 * Override empty parent method to log validation errors for debugging
	 * purposes only. Can be removed.
	 */
	protected void onBindAndValidate(HttpServletRequest request,
			Object command, BindException errors) throws Exception {
		SpringValidatorUtils.logValidationErrors(log, errors);
		InspectionCriteria inspectionCriteria = (InspectionCriteria) command;

		// Calculating the fee will trigger additional validation by the
		// PriceAdjuster implementations, and any exceptions should be shown to
		// user
		try {
			inspectionCriteria.getInspectorCompany().calculateInspectionFee(
					inspectionCriteria.getInspection());
		} catch (PriceAdjusterException e) {
			// TODO: Externalize/create codes for each exception
			errors.reject("dummy.price.error.code", e.getMessage());
		}
	}

	protected void initBinder(HttpServletRequest request,
			ServletRequestDataBinder binder) throws Exception {
		// Feb 4, 2008 8:00:00 AM
		DateFormat df = new SimpleDateFormat("MMM dd, yyyy hh:mm:ss a");
		df.setLenient(true);
		CustomDateEditor editor = new CustomDateEditor(df, false);
		binder.registerCustomEditor(Date.class, editor);
		binder.registerCustomEditor(State.class, new CustomStateEditor());

		binder.registerCustomEditor(Double.class,
				"inspection.property.squareFeet",
				new FlexibleNumberPropertyEditor("###,###,##0", Double.class));
		binder.registerCustomEditor(Float.class,
				"inspection.property.listedPrice",
				new FlexibleNumberPropertyEditor("###,###,##0", Float.class));
	}

	private List<State> findServedStates(InspectorCompany inspectorCompany) {
		Set<String> nearbyStates = zipCodeSearchService
				.findStatesByZipCodeRadiusInMiles(inspectorCompany.getAddress()
						.getZipCode(), inspectorCompany.getMaxMilesServed());
		return prepareServedStatesMap(nearbyStates);
	}

	private List<State> prepareServedStatesMap(Set<String> nearbyStates) {
		List<State> servedStates = new ArrayList<State>(nearbyStates.size());
		for (String stateCode : nearbyStates) {
			State blu = State.valueOf(stateCode);
			servedStates.add(State.valueOf(stateCode));
		}
		return servedStates;
	}

	public void setZipCodeSearchService(
			ZipCodeSearchService zipCodeSearchService) {
		this.zipCodeSearchService = zipCodeSearchService;
	}

	public void setFactory(Factory factory) {
		this.factory = factory;
	}

	public void setRepository(Repository repository) {
		this.repository = repository;
	}

}
